package org.snail.serialization;

import com.google.common.collect.Maps;
import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;

import java.util.concurrent.ConcurrentMap;

/**
 *
 * 序列化,目前只支持protostuff序列化
 *
 * Created by fish on 17/8/18.
 */
public class Serializer {

    /**
     * The max buffer size
     */
    private static final int MAX_CACHED_BUF_SIZE = 256 * 1024;

    /**
     * The default buffer size
     */
    private static final int DEFAULT_BUF_SIZE = 512;


    private static final ConcurrentMap<Class<?>, Schema<?>> schemaCache = Maps.newConcurrentMap();

    //复用LinkedBuffer中链表头结点 byte[]
    private static final ThreadLocal<LinkedBuffer> bufThreadLocal = new ThreadLocal<LinkedBuffer>() {

        @Override
        protected LinkedBuffer initialValue() {
            return LinkedBuffer.allocate(DEFAULT_BUF_SIZE);
        }
    };


    static {
        // 默认 true, 禁止反序列化时构造方法被调用, 防止有些类的构造方法内意想不到的结果。
        System.setProperty("protostuff.runtime.always_use_sun_reflection_factory","true");
        // 默认 false, 不允许数组中的元素为 null 。
        System.setProperty("protostuff.runtime.allow_null_array_element", "false");
    }


    public <T> byte[] writeObject(T obj) {
        Schema<T> schema = getSchema((Class<T>) obj.getClass());
        LinkedBuffer buf = bufThreadLocal.get();
        try {
            return ProtostuffIOUtil.toByteArray(obj, schema, buf);
        } finally {
            buf.clear();
        }
    }


    public <T> T readObject(byte[] bytes, int offset, int length, Class<T> clazz) {
        T msg = null;
        try {
            msg = clazz.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        Schema<T> schema = getSchema(clazz);
        ProtostuffIOUtil.mergeFrom(bytes, offset, length, msg, schema);
        return msg;
    }


    public <T> T readObject(byte[] bytes, Class<T> clazz) {
        return readObject(bytes, 0, bytes.length, clazz);
    }


    private <T> Schema<T> getSchema(Class<T> clazz) {
        Schema<T> schema = (Schema<T>) schemaCache.get(clazz);
        if (schema == null) {
            Schema<T> newSchema = RuntimeSchema.createFrom(clazz);
            schema = (Schema<T>) schemaCache.putIfAbsent(clazz, newSchema);
            if (schema == null) {
                schema = newSchema;
            }
        }
        return schema;
    }



}
